﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using memcached.Tx;

namespace memcached
{
    /// <summary>
    /// Provide means for read-committed-alike transactional cache operations.
    /// </summary>
    /// <remarks>
    /// May loose expiry information in some operations during Rollback.
    /// Each operation can be in a different transaction (and thread).
    /// Thread-safe on itself but store operations are not.
    /// </remarks>
    public sealed class TxCache : ICacheAdvanced
    {
        /// <summary>
        /// Initialize.
        /// </summary>
        /// <param name="store">read (not in-proc) storage</param>
        [System.Diagnostics.DebuggerHidden]
        public TxCache(ICache store)
        {
            if (store == null)
                throw new ArgumentNullException("store");
            real = store;
        }

        #region Private
        private readonly ICache real;

        /// <summary>
        ///  We can have many transactions ongoing on a single thread.
        ///  But each TransactionScope is thread-limited.
        /// </summary>
        [ThreadStatic]
        private static Dictionary<string, TxCacheResourceManager> _enlistments;

        private static TxCacheResourceManager GetEnlistment()
        {
            System.Transactions.Transaction tx = System.Transactions.Transaction.Current;
            if (tx == null)
                throw new InvalidOperationException("TxCache can only use used inside TransactionScope.");
            if (tx.IsolationLevel != System.Transactions.IsolationLevel.ReadCommitted &&
                tx.IsolationLevel != System.Transactions.IsolationLevel.RepeatableRead)
                throw new InvalidOperationException("Isolation level not supported.");
            if (tx.TransactionInformation.Status != System.Transactions.TransactionStatus.Active)
                return null;
            // no need to lock here, as we have one value per thread
            if (_enlistments == null)
                _enlistments = new Dictionary<string, TxCacheResourceManager>(1);
            TxCacheResourceManager enlistment;
            string id = tx.TransactionInformation.LocalIdentifier;
            if (!_enlistments.TryGetValue(id, out enlistment))
            {
                enlistment = new TxCacheResourceManager(tx);// new transaction, will enlist here
                _enlistments.Add(id, enlistment);
                enlistment.OnCleanup((transId) =>
                {
                    if (_enlistments != null)
                        _enlistments.Remove(transId);
                });
            }
            return enlistment;
        }
        #endregion

        #region ICacheAdvanced
        /// <summary></summary>
        public long Increment(string key, long startAt)
        {
            if (!(real is ICacheAdvanced))
                throw new NotSupportedException("Requires ICacheAdvanced store");
            var e = GetEnlistment();
            if (e == null) return -1;
            var op = new IncrementOp((ICacheAdvanced)real, e.Temporary, key, startAt);
            return (long)e.EnlistOperation(op);
        }

        /// <summary></summary>
        public long Decrement(string key)
        {
            if (!(real is ICacheAdvanced))
                throw new NotSupportedException("Requires ICacheAdvanced store");
            var e = GetEnlistment();
            if (e == null) return 0;
            var op = new DecrementOp((ICacheAdvanced)real, e.Temporary, key);
            return (long)e.EnlistOperation(op);
        }

        /// <summary></summary>
        public bool Remove(string key)
        {
            var e = GetEnlistment();
            if (e == null) return false;
            var op = new RemoveOp(real, e.Temporary, key);
            return (bool)e.EnlistOperation(op);
        }

        /// <summary></summary>
        public bool Set(string key, object value)
        {
            var e = GetEnlistment();
            if (e == null) return false;
            var op = new SetOp(real, e.Temporary, key, value);
            return (bool)e.EnlistOperation(op);
        }

        /// <summary></summary>
        public bool Set(string key, int validFor, object value)
        {
            var e = GetEnlistment();
            var op = new SetExOp(real, e.Temporary, key, value, validFor);
            return (bool)e.EnlistOperation(op);
        }

        /// <summary></summary>
        public bool Set(string key, DateTime expireAt, object value)
        {
            var e = GetEnlistment();
            if (e == null) return false;
            var op = new SetExOp(real, e.Temporary, key, value, expireAt);
            return (bool)e.EnlistOperation(op);
        }

        /// <summary></summary>
        public bool Set(string key, ulong cas, object value)
        {
            if (!(real is ICacheAdvanced))
                throw new NotSupportedException("Requires ICacheAdvanced store");
            var e = GetEnlistment();
            if (e == null) return false;
            var op = new SetExOp((ICacheAdvanced)real, e.Temporary, key, value, cas);
            return (bool)e.EnlistOperation(op);
        }

        /// <summary></summary>
        public bool Add(string key, int validFor, object value)
        {
            var e = GetEnlistment();
            if (e == null) return false;
            var op = new AddOp(real, e.Temporary, key, value, validFor);
            return (bool)e.EnlistOperation(op);
        }

        /// <summary></summary>
        public bool Add(string key, DateTime expireAt, object value)
        {
            var e = GetEnlistment();
            if (e == null) return false;
            var op = new AddOp(real, e.Temporary, key, value, expireAt);
            return (bool)e.EnlistOperation(op);
        }

        /// <summary></summary>
        public T Get<T>(string key, out bool success)
        {
            var e = GetEnlistment();
            if (e == null) { success = false; return default(T); }
            var op = new GetOp<T>(real, e.Temporary, key);
            object res = e.EnlistOperation(op);
            success = op.Success;
            if (res != null && res is T)
                return (T)res;
            return default(T);
        }

        /// <summary></summary>
        public object Get(string key, out bool success)
        {
            var e = GetEnlistment();
            if (e == null) { success = false; return false; }
            var op = new GetOp(real, e.Temporary, key);
            object v = e.EnlistOperation(op);
            success = op.Success;
            return v;
        }

        /// <summary></summary>
        public CasValue<T> Get<T>(string key)
        {
            if (!(real is ICacheAdvanced))
                throw new NotSupportedException("Requires ICacheAdvanced store");
            var e = GetEnlistment();
            if (e == null) return null;
            var op = new GetCasOp<T>((ICacheAdvanced)real, e.Temporary, key);
            return e.EnlistOperation(op) as CasValue<T>;
        }
        #endregion
    }
}